extern crate regex;
use chrono::prelude::*;
use rand::Rng;
use regex::Regex;
use reqwest::header::{
    HeaderMap, HeaderValue, ACCEPT_LANGUAGE, CONNECTION, CONTENT_TYPE, USER_AGENT,
};
use serde::{Deserialize, Serialize};
use serde_xml_rs;
use std::{fs::File, io::Write, ops::Deref, path::Path, vec};

#[derive(Debug, Default, Deserialize)]
#[serde(rename = "error")]
struct NewLoginPage {
    pub ret: i32,
    pub message: String,
    pub skey: String,
    pub wxsid: String,
    pub wxuin: u32,
    pub pass_ticket: String,
    pub isgrayscale: i32,
}

#[derive(Debug, Default, Deserialize)]
struct Member {
    #[serde(rename(serialize = "UserName", deserialize = "UserName"))]
    pub user_name: String,
    #[serde(rename(serialize = "NickName", deserialize = "NickName"))]
    pub nick_name: String,
    #[serde(rename(serialize = "Sex", deserialize = "Sex"))]
    pub sex: i32,
    #[serde(rename(serialize = "RemarkName", deserialize = "RemarkName"))]
    pub remark_name: String,
}

#[derive(Debug, Default, Deserialize)]
struct BaseResponse {
    #[serde(rename(serialize = "Ret", deserialize = "Ret"))]
    pub ret: i32,
    #[serde(rename(serialize = "ErrMsg", deserialize = "ErrMsg"))]
    pub err_msg: String,
}
#[derive(Debug, Default, Deserialize)]
struct ContactMsg {
    #[serde(rename(serialize = "BaseResponse", deserialize = "BaseResponse"))]
    pub base_response: BaseResponse,
    #[serde(rename(serialize = "MemberCount", deserialize = "MemberCount"))]
    pub member_count: i32,
    #[serde(rename(serialize = "MemberList", deserialize = "MemberList"))]
    pub member_list: Vec<Member>,
}

// SyncKey
#[derive(Debug, Default, Copy, Clone, Deserialize, Serialize)]
struct Key {
    #[serde(rename(serialize = "Key", deserialize = "Key"))]
    pub key: i32,
    #[serde(rename(serialize = "Val", deserialize = "Val"))]
    pub value: i64,
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
struct SyncKey {
    #[serde(rename(serialize = "Count", deserialize = "Count"))]
    pub count: i32,
    #[serde(rename(serialize = "List", deserialize = "List"))]
    pub list: Vec<Key>,
}

#[derive(Debug, Default, Clone, Deserialize)]
struct SyncKeyMsg {
    #[serde(rename(serialize = "SyncKey", deserialize = "SyncKey"))]
    pub sync_key: SyncKey,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut headers = HeaderMap::new();
    headers.insert(USER_AGENT, HeaderValue::from_str("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36").unwrap());
    headers.insert(
        ACCEPT_LANGUAGE,
        HeaderValue::from_str("zh-CN,zh;q=0.9").unwrap(),
    );
    headers.insert(
        CONNECTION,
        HeaderValue::from_str("Connection: keep-alive").unwrap(),
    );
    // headers.insert(ACCEPT, HeaderValue::from_str("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9").unwrap());

    let client = reqwest::Client::builder()
        .default_headers(headers)
        .cookie_store(true)
        .build()?;
    let res = client.get("https://wx.qq.com").send().await;

    let html = res.unwrap().text().await.unwrap();

    // 获取appid需要的参数
    let re = Regex::new(
        r#"<script src="//res.wx.qq.com/a/wx_fed/webwx/res/static/js/index_([\da-z]*).js">"#,
    )
    .unwrap();
    let caps = re.captures(&html);
    let id = caps.unwrap().get(1).map_or("", |v| v.as_str());
    println!("id:{:?}", id);

    // 获取appid
    let res = client
        .get(&format!(
            "https://res.wx.qq.com/a/wx_fed/webwx/res/static/js/index_{}.js",
            id
        ))
        .send()
        .await;
    let html = res.unwrap().text().await.unwrap();
    let re = Regex::new(r#""/jslogin\?appid=([\da-z]*)&redirect_uri=""#).unwrap();
    let caps = re.captures(&html);
    let appid = caps.unwrap().get(1).map_or("", |v| v.as_str());
    println!("appid:{:?}", appid);

    // 获取二维码连接需要的参数 uuid
    //https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1613736192734

    let res = client.get(&format!("https://login.wx.qq.com/jslogin?appid={}&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_={}", appid, Local::now().timestamp_millis()))
    .send().await;
    let html = res.unwrap().text().await.unwrap();
    let re = Regex::new(r#"QRLogin.uuid = "(.+?)";"#).unwrap();
    let caps = re.captures(&html);
    let uuid = caps.unwrap().get(1).map_or("", |v| v.as_str());
    println!("uuid:{:?}", uuid);

    // 获取验证码图片
    // https://login.weixin.qq.com/qrcode/{uuid}
    let res = client
        .get(&format!("https://login.weixin.qq.com/qrcode/{}", uuid))
        .send()
        .await;

    let path = Path::new("./qrcode.png");

    let mut file = match File::create(&path) {
        Err(why) => panic!("couldn't create {}", why),
        Ok(file) => file,
    };
    let content = res.unwrap().bytes().await?;
    file.write_all(&content)?;

    // 等待验证结果
    let mut rng = rand::thread_rng();
    // 用户列表
    let contact: ContactMsg;
    // 包含pass_ticket  skey 等信息
    let new_login_page: NewLoginPage;
    let sync_key_msg: SyncKeyMsg;

    let mut seq: i64 = 0; // 轮询编号
    loop {
        // https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=gbFJLxDScg==&tip=0&r=1161118882&_=1613746558746
        let res = client
            .get(&format!("https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={}&tip=0&r={}&_={}", uuid, rng.gen::<u32>(), Local::now().timestamp_millis()))
            .send()
            .await;
        let html = res.unwrap().text().await.unwrap();
        println!("result:{}", html);

        let re = Regex::new(r#"window.code=(\d+?);"#).unwrap();
        let caps = re.captures(&html);
        let code = caps.unwrap().get(1).map_or("", |v| v.as_str());
        println!("code:{:?}", code);
        if code != "200" {
            println!("请扫码或确认登陆");
            continue;
        }

        // 获取要跳转的url
        // window.redirect_uri="https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A7qi1213HEg9rNxyVCLZpv14@qrticket_0&uuid=ofKYdwQdHw==&lang=zh_CN&scan=1613748432";
        let re = Regex::new(r#"window.redirect_uri="(.+?)";"#).unwrap();
        let caps = re.captures(&html);
        let url = caps.unwrap().get(1).map_or("", |v| v.as_str());
        println!("url:{:?}", url);

        // 获取跳转url的内容
        let res = client
            .get(&format!("{}&fun=new&version=v2&lang=zh_CN", url))
            .send()
            .await;
        let html = res.unwrap().text().await.unwrap();
        println!("result:{}", html);

        new_login_page = serde_xml_rs::from_str(&html)?;
        println!("{:#?}", new_login_page);

        // 微信初始化
        // https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=819463288&lang=zh_CN&pass_ticket=fdWqaEQ%252B8CQQjcgUXKijmZAh5Wy08uTJg4x6FRvcavR9dOxUop%252F0qrIwH4bRCsEs
        let r: u32 = rng.gen_range(100000000..=999999999);
        let url = &format!(
            "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r={}&lang=zh_CN&pass_ticket={}",
            r, new_login_page.pass_ticket
        );
        println!("webwxinit url:{}", url);
        let res = client
            .post(url)
            .body(format!(
                r#"{{"BaseRequest":{{"Uin":"{}","Sid":"{}","Skey":"{}","DeviceID":"{}" }}}}"#,
                new_login_page.wxuin,
                new_login_page.wxsid,
                new_login_page.skey,
                new_device_id()
            ))
            .send()
            .await;
        let html = res.unwrap().text().await.unwrap();

        /*
            "SyncKey": {
            "Count": 4,
            "List": [{
                "Key": 1,
                "Val": 739514317
            }, {
                "Key": 2,
                "Val": 739514741
            }, {
                "Key": 3,
                "Val": 739514739
            }, {
                "Key": 1000,
                "Val": 1614084556
            }]
        },
        */
        println!("wxinit response:{}", html);

        sync_key_msg = serde_json::from_str(&html).unwrap();
        // 记录轮询编号
        for v in sync_key_msg.sync_key.list.clone() {
            if v.key == 2 {
                seq = v.value;
            }
        }
        // println!("wxinit response json:{:#?}", sync_key_msg);

        // 获取用户列表
        // https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&pass_ticket=KUAK6GDWd5KsWjmJmMUsm0f%252FMNXWfJ9Q1eDAtrDtSgsSMLeo9h8qE8GRM8lXcbJb&r=1613782993809&seq=0&skey=@crypt_d0e0334f_05ba8167ac857c1b438cddca2f7c3aff
        let res = client
            .get(&format!("https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&pass_ticket={}&r={}&seq=0&skey={}", new_login_page.pass_ticket, Local::now().timestamp_millis(), new_login_page.skey))
            .send()
            .await;
        let html = res.unwrap().text().await.unwrap();
        // println!("contact:{}", html);
        contact = serde_json::from_str(&html).unwrap();
        println!("共 {:#?} 个成员", contact.member_count);
        break;
    }

    // 循环获取消息
    loop {
        // https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1614081038997&skey=%40crypt_d0e0334f_667df7717ecaa74d9367bace73954ac0&sid=truYQBQOGAdU1ahr&uin=887365730&deviceid=e207673849973146&synckey=1_739514317%7C2_739514469%7C3_739514407%7C11_739514333%7C19_5992%7C201_1614080661%7C206_103%7C1000_1614078722%7C1001_1614078794&_=1614080646290
        // 'e' + ("" + Math.random().toFixed(15)).substring(2, 17);
        // getFormateSyncCheckKey: function() {

        //     var synncheckkey = _synccheckkey || this.getSyncKey();
        //     var synccheckkeyList =synncheckkey.List,
        //         synccheckkeyFormat = [];

        //     for (var i = 0,len = synccheckkeyList.length; i < len; i++) {
        //         synccheckkeyFormat.push(synccheckkeyList[i].Key + "_" + synccheckkeyList[i].Val);
        //     }
        //     return synccheckkeyFormat.join('|');
        // },

        let sync_key = sync_key_msg.clone().sync_key;

        let url = &format!("https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck?r={}&skey={}&sid={}&uin={}&deviceid=e{}&synckey={}",
        Local::now().timestamp_millis(),  new_login_page.skey.replace("@", "%40").replace("+", "%2B"), new_login_page.wxsid.replace("@", "%40").replace("+", "%2B"), new_login_page.wxuin, new_device_id(),gen_sync_key_str(sync_key, seq));
        seq+=1;
        println!("轮询url:{}", url);
        // 轮询，获取事件
        let res = client.get(url).send().await;
        let html = match res.unwrap().text().await {
            Ok(v) => v,
            Err(e) => {
                println!("没消息：{}", format!("{}", e));
                continue;
            }
        };

        println!("轮询内容:{}", html);

        if html == r#"window.synccheck={retcode:"0",selector:"0"}"# {
            continue;
        }

        // 有新事件到来，获取具体信息
        // {"BaseRequest":{"Uin":887365730,"Sid":"y/baAVpy9e/Mu+57","Skey":"@crypt_d0e0334f_e442a3aa0856c6c7d70023da7f33e31c","DeviceID":"e157545975822942"},"SyncKey":{},"rr":818535544}
        let url = &format!("https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid={}&skey={}&lang=zh_CN&pass_ticket={}",
        new_login_page.wxsid.replace("@", "%40").replace("+", "%2B"),new_login_page.skey.replace("@", "%40"),  new_login_page.pass_ticket);
        println!("webwxsync url:{}", url);

        let rr = rng.gen_range(100000000..=999999999);

        let body = format!(
            r#"{{"BaseRequest":{{"Uin":"{}","Sid":"{}","Skey":"{}","DeviceID":"{}"}},"SyncKey":{},"rr":{}}}"#,
            new_login_page.wxuin,
            new_login_page.wxsid,
            new_login_page.skey,
            new_device_id(),
            serde_json::to_string(&sync_key_msg.clone().sync_key).unwrap(),
            rr
        );
        println!("webwxsync body:{}", body);

        let res = client
            .post(url)
            .header(CONTENT_TYPE, "application/json;charset=UTF-8")
            .body(body)
            .send()
            .await;
        let html = res.unwrap().text().await.unwrap();
        println!("收到消息:{}", html);
    }

    Ok(())
}

#[inline]
fn new_device_id() -> String {
    let mut rng = rand::thread_rng();
    let r: u64 = rng.gen_range(100000000000000..=999999999999999);
    format!("e{}", r)
}

/*
        getFormateSyncCheckKey: function() {

            var synncheckkey = _synccheckkey || this.getSyncKey();
            var synccheckkeyList =synncheckkey.List,
                synccheckkeyFormat = [];

            for (var i = 0,len = synccheckkeyList.length; i < len; i++) {
                synccheckkeyFormat.push(synccheckkeyList[i].Key + "_" + synccheckkeyList[i].Val);
            }
            return synccheckkeyFormat.join('|');
        },
*/
fn gen_sync_key_str(sync_key: SyncKey, seq: i64) -> String {
    println!("seqqqqqqqqqqqqqqqqqq:{}", seq);
    let mut list = vec![];
    for v in sync_key.list {
        if v.key == 2 {
            list.push(format!("{}_{}", v.key, seq));
        } else if v.key == 201 {
            list.push(format!("{}_{}", v.key, Local::now().timestamp()));
        } else {
            list.push(format!("{}_{}", v.key, v.value));
        }
    }
    list.join("%7C").to_string()
}

mod test {
    use crate::SyncKeyMsg;
    #[test]
    fn test_sync_key() {
        let msg = SyncKeyMsg::default();
        let v = serde_json::to_string(&msg.sync_key).unwrap();
        println!("{}", v);
    }
}
